package fr.sedoo.rbv.metadataws;

import java.io.ByteArrayOutputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.constraint.DefaultConstraints;
import org.apache.sis.util.iso.SimpleInternationalString;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.opengis.metadata.constraint.LegalConstraints;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.util.InternationalString;

import fr.sedoo.commons.metadata.shared.ResourceIdentifier;
import fr.sedoo.commons.metadata.shared.ResourceLink;
import fr.sedoo.commons.metadata.utils.domain.Contact;
import fr.sedoo.commons.metadata.utils.domain.DescribedURL;
import fr.sedoo.commons.metadata.utils.domain.MetadataTools;

public class RBVMarshaller implements RBVConstants{


	public static String toRBV(RBVMetadata metadata) throws RBVException
	{
		try
		{
			Element root = new Element(METADATA_NODE_NAME);
			addNonNullAttribute(root, METADATA_LANGUAGE_ATTR_NAME, StringUtils.trimToEmpty(metadata.getMetadataLanguage()));
			Element level = new Element(LEVEL_NODE_NAME);
			if (metadata.isObservatory())
			{
				level.setAttribute(LEVEL_TYPE_ATTR_NAME, RBVMetadata.OBSERVATORY);
			}
			else if (metadata.isExperimentalSite())
			{
				root.setAttribute(LEVEL_TYPE_ATTR_NAME, RBVMetadata.EXPERIMENTAL_SITE);
			}
			else
			{
				root.setAttribute(LEVEL_TYPE_ATTR_NAME, RBVMetadata.DATASET);
			}

			Document doc = new Document(root);
			root.addContent(getIdentificationPart(metadata, root));
			root.addContent(getMetametadataPart(metadata));
			root.addContent(getTemporalPart(metadata));
			root.addContent(getLocalisationPart(metadata));
			root.addContent(getConstraintPart(metadata));
			root.addContent(getOthersPart(metadata));

			XMLOutputter xmlOutput = new XMLOutputter();
			xmlOutput.setFormat(Format.getPrettyFormat().setEncoding("UTF-8"));

			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

			xmlOutput.output(doc, outputStream);

			return new String(outputStream.toByteArray(), "UTF-8").trim();

		}
		catch (Throwable t)
		{
			throw new RBVException(t);
		}
	}

	private static Element getIdentificationPart(RBVMetadata metadata, Element root) 
	{
		Element identification = new Element(IDENTIFICATION_NODE_NAME);

		DefaultCitation citation = MetadataTools.getCitation(metadata);
		InternationalString titles = citation.getTitle();
		addInternationalisedString(metadata, titles, identification, RESOURCE_TITLE_NODE_NAME, RESOURCE_I18N_TITLE_NODE_NAME);

		InternationalString resourceAbstract = MetadataTools.getFisrtIdentificationInfo(metadata).getAbstract();
		addInternationalisedString(metadata, resourceAbstract, identification, RESOURCE_ABSTRACT_NODE_NAME, RESOURCE_I18N_ABSTRACT_NODE_NAME);
		
		addNonNullContent(identification, RESOURCE_STATUS_NODE_NAME, StringUtils.trimToEmpty(metadata.getStatus()));
		addNonNullContent(identification, RESOURCE_UPDATE_RYTHM_NODE_NAME, StringUtils.trimToEmpty(metadata.getResourceUpdateRythm()));
		//		
		addLinkList(identification, metadata.getResourceURL(), RESOURCE_URLS_NODE_NAME, RESOURCE_URL_NODE_NAME);
		addSnapshotList(identification, metadata.getSnapshotURL(), RESOURCE_SNAPSHOTS_NODE_NAME, RESOURCE_SNAPSHOT_NODE_NAME, root);

		addContactList(identification, metadata.getResourceContacts());
		if (metadata.getResourceIdentifiers().isEmpty() == false)
		{
			Element identifiers = new Element(IDENTIFIERS_NODE_NAME);
			Iterator<ResourceIdentifier> iterator = metadata.getResourceIdentifiers().iterator();
			while (iterator.hasNext()) 
			{
				ResourceIdentifier resourceIdentifier = (ResourceIdentifier) iterator.next();
				if ((resourceIdentifier.getNameSpace().startsWith(RBVMetadata.OBSERVATORY_RBV_NAMESPACE)) || (resourceIdentifier.getNameSpace().startsWith(RBVMetadata.EXPERIMENTAL_SITE_RBV_NAMESPACE)))
				{
					Element level = new Element(LEVEL_NODE_NAME);
					if (resourceIdentifier.getNameSpace().startsWith(RBVMetadata.OBSERVATORY_RBV_NAMESPACE))
					{
						level.setAttribute(LEVEL_TYPE_ATTR_NAME, RBVMetadata.OBSERVATORY);
					}
					if (resourceIdentifier.getNameSpace().startsWith(RBVMetadata.EXPERIMENTAL_SITE_RBV_NAMESPACE))
					{
						level.setAttribute(LEVEL_TYPE_ATTR_NAME, RBVMetadata.EXPERIMENTAL_SITE);
					}
					level.setAttribute(LEVEL_NAME_ATTR_NAME, resourceIdentifier.getCode());
					root.addContent(level);
				}
				else
				{
					Element aux = new Element(IDENTIFIER_NODE_NAME);
					addNonNullAttribute(aux, IDENTIFIER_CODE_ATTR_NAME, StringUtils.trimToEmpty(resourceIdentifier.getCode()));
					addNonNullAttribute(aux, IDENTIFIER_NAMESPACE_ATTR_NAME, StringUtils.trimToEmpty(resourceIdentifier.getNameSpace()));
					identifiers.addContent(aux);
				}
			}
			if (!identifiers.getChildren().isEmpty())
			{
				identification.addContent(identifiers);
			}
		}

		return identification;
	}


	private static void addInternationalisedString(RBVMetadata metadata, InternationalString internationalString,
			Element identification, String simpleNodeName,	String i18nNodeName) {

		
		if (MetadataTools.isI18n(internationalString) == false)
		{
			addNonNullContent(identification, simpleNodeName, MetadataTools.getDefaultValue(internationalString));
		}
		else
		{
			Element i18nTitles = new Element(i18nNodeName);
			Map<String, String> existingValues = MetadataTools.getExistingValues(internationalString, metadata.getMetadataLanguage());
			Iterator<String> iterator = existingValues.keySet().iterator();
			while (iterator.hasNext()) 
			{
				String language = (String) iterator.next();
				String value = StringUtils.trimToEmpty(existingValues.get(language));
				if (StringUtils.isEmpty(value) == false)
				{
					Element translation = new Element(TRANSLATION_NODE_NAME);
					translation.setAttribute(LANGUAGE_ATTR_NAME, language);
					translation.setText(value);
					i18nTitles.addContent(translation);
				}
			}
			identification.addContent(i18nTitles);
		}
		
	}

	private static Element getMetametadataPart(RBVMetadata metadata) 
	{
		Element metamatadata = new Element(METAMETADATA_NODE_NAME);
		addNonNullContent(metamatadata, METADATA_UUID_NODE_NAME, StringUtils.trimToEmpty(metadata.getUuid()));
		addNonNullContent(metamatadata, METADATA_PARENT_UUID_NODE_NAME, StringUtils.trimToEmpty(metadata.getParentIdentifier()));
		addNonNullContent(metamatadata, METADATA_LAST_MODIFICATION_DATE_NODE_NAME, StringUtils.trimToEmpty(metadata.getMetadataDate()));
		addContactList(metamatadata, metadata.getMetadataContacts());
		return metamatadata;
	}

	private static Element getTemporalPart(RBVMetadata metadata) 
	{
		Element temporal = new Element(TEMPORAL_NODE_NAME);
		addNonNullContent(temporal, RESOURCE_BEGIN_DATE, StringUtils.trimToEmpty(metadata.getResourceBeginDate()));
		addNonNullContent(temporal, RESOURCE_END_DATE, StringUtils.trimToEmpty(metadata.getResourceEndDate()));
		//TODO Last revision date
		return temporal;
	}

	private static Element getOthersPart(RBVMetadata metadata) 
	{
		Element others = new Element(OTHERS_NODE_NAME);
		addStringList(others, metadata.getResourceLanguages(), LANGUAGES_NODE_NAME, LANGUAGE_NODE_NAME);
		addNonNullContent(others, RESOURCE_ENCODING_CHARSET_NODE_NAME, StringUtils.trimToEmpty(metadata.getResourceEncodingCharset()));

		org.opengis.metadata.distribution.Format format = metadata.getResourceFormat();
		if (format != null)
		{
			Element formatNode = new Element(RESOURCE_FORMAT_NODE_NAME);
			if (format.getName()!= null)
			{
				addNonNullAttribute(formatNode, NAME_ATTR_NAME, StringUtils.trimToEmpty(format.getName().toString()));
			}
			if (format.getVersion()!= null)
			{
				addNonNullAttribute(formatNode, VERSION_ATTR_NAME, StringUtils.trimToEmpty(format.getVersion().toString()));
			}
			others.addContent(formatNode);
		}

		addNonNullContent(others, CREATION_DATE_NODE_NAME, StringUtils.trimToEmpty(metadata.getCreationDate()));
		addNonNullContent(others, LAST_REVISION_DATE_NODE_NAME, StringUtils.trimToEmpty(metadata.getLastRevisionDate()));
		addNonNullContent(others, PUBLICATION_DATE_NODE_NAME, StringUtils.trimToEmpty(metadata.getPublicationDate()));
		addNonNullContent(others, COORDINATE_SYSTEM_NODE_NAME, StringUtils.trimToEmpty(metadata.getCoordinateSystem()));

		return others;
	}

	private static Element getConstraintPart(RBVMetadata metadata) 
	{
		Element constraints = new Element(CONSTRAINTS_NODE_NAME);
		
		DefaultConstraints useConditionConstraint = MetadataTools.getUseConditionConstraint(metadata);
		Collection<InternationalString> useLimitations = useConditionConstraint.getUseLimitations();
		if (useLimitations.isEmpty())
		{
			addInternationalisedString(metadata, new SimpleInternationalString(""), constraints, USE_CONDITIONS_NODE_NAME, I18N_USE_CONDITIONS_NODE_NAME);
		}
		else
		{
			addInternationalisedString(metadata, useLimitations.iterator().next(), constraints, USE_CONDITIONS_NODE_NAME, I18N_USE_CONDITIONS_NODE_NAME);
		}
		
		LegalConstraints legalConstraints = MetadataTools.getPublicAccessLimitationConstraint(metadata);
		Collection<? extends InternationalString> otherConstraints = legalConstraints.getOtherConstraints();
		if (useLimitations.isEmpty())
		{
			addInternationalisedString(metadata, new SimpleInternationalString(""), constraints, PUBLIC_ACCESS_LIMITATIONS_NODE_NAME, I18N_PUBLIC_ACCESS_LIMITATIONS_NODE_NAME);
		}
		else
		{
			addInternationalisedString(metadata, otherConstraints.iterator().next(), constraints, PUBLIC_ACCESS_LIMITATIONS_NODE_NAME, I18N_PUBLIC_ACCESS_LIMITATIONS_NODE_NAME);
		}
		
		return constraints;
	}

	private static Element getLocalisationPart(RBVMetadata metadata) 
	{
		Element localisation = new Element(LOCALISATION_NODE_NAME);
		List<GeographicBoundingBox> geographicBoundingBoxes = metadata.getGeographicBoundingBoxes();
		Iterator<GeographicBoundingBox> iterator = geographicBoundingBoxes.iterator();
		while (iterator.hasNext()) 
		{
			GeographicBoundingBox geographicBoundingBox = (GeographicBoundingBox) iterator.next();
			if (geographicBoundingBox != null)
			{
				Element box = new Element(GEOGRAPHICAL_BOX_NODE_NAME);
				box.setAttribute(NORTH_ATTR_NAME, doubleOrEmpty(geographicBoundingBox.getNorthBoundLatitude()));
				box.setAttribute(SOUTH_ATTR_NAME, doubleOrEmpty(geographicBoundingBox.getSouthBoundLatitude()));
				box.setAttribute(EAST_ATTR_NAME, doubleOrEmpty(geographicBoundingBox.getEastBoundLongitude()));
				box.setAttribute(WEST_ATTR_NAME, doubleOrEmpty(geographicBoundingBox.getWestBoundLongitude()));
				localisation.addContent(box);
			}	
		}

		return localisation;
	}

	private static String doubleOrEmpty(Double value) 
	{
		if (value ==  null)
		{
			return "";
		}
		if (value.isNaN())
		{
			return "";
		}
		else
		{
			return value.toString();
		}
	}

	private static void addContactList(Element parent, List<Contact> contactList) 
	{
		if (contactList != null)
		{
			Element contacts = new Element(CONTACTS_NODE_NAME);
			parent.addContent(contacts);
			Iterator<Contact> iterator = contactList.iterator();
			while (iterator.hasNext()) 
			{
				Contact contact = iterator.next();
				Element aux = new Element(CONTACT_NODE_NAME);
				aux.setAttribute(CONTACT_EMAIL_ATTR_NAME, StringUtils.trimToEmpty(contact.getEmailAddress()));
				aux.setAttribute(CONTACT_INDIVIDUAL_ATTR_NAME, StringUtils.trimToEmpty(contact.getIndividualName()));
				aux.setAttribute(CONTACT_ORGANISATION_ATTR_NAME, StringUtils.trimToEmpty(contact.getOrganisationName()));
				aux.setAttribute(CONTACT_ROLE_ATTR_NAME, StringUtils.trimToEmpty(contact.getRole()));
				contacts.addContent(aux);
			}
		}

	}

	private static void addLinkList(Element parent, List<? extends ResourceLink> resourceURL, String listeParentNodeName, String listNodeName) 
	{
		if ((resourceURL != null) && (!resourceURL.isEmpty()))
		{
			Iterator<? extends ResourceLink> iterator = resourceURL.iterator();
			Element urls = new Element(listeParentNodeName);
			parent.addContent(urls);
			while (iterator.hasNext()) 
			{
				ResourceLink describedURL = iterator.next();
				Element e = new Element(listNodeName);
				e.setAttribute(LINK_ATTR_NAME, describedURL.getLink());
				e.setAttribute(LABEL_ATTR_NAME, describedURL.getLabel());
				e.setAttribute(PROTOCOL_ATTR_NAME, RBVResourceLink.getRBVProtocol(describedURL.getProtocol()));
				urls.addContent(e);
			}
		}

	}

	private static void addSnapshotList(Element parent, List<? extends DescribedURL> resourceURL, String listeParentNodeName, String listNodeName, Element root) 
	{
		if ((resourceURL != null) && (!resourceURL.isEmpty()))
		{
			Iterator<? extends DescribedURL> iterator = resourceURL.iterator();
			Element urls = new Element(listeParentNodeName);
			while (iterator.hasNext()) 
			{
				DescribedURL describedURL = iterator.next();
				if (StringUtils.trimToEmpty(describedURL.getLabel()).compareToIgnoreCase(RBVMetadata.LOGO)==0)
				{
					Element e = new Element(LOGO_NODE_NAME);
					e.setAttribute(LOGO_URL_ATTR_NAME, describedURL.getLink());
					root.addContent(e);
				}
				else
				{
					Element e = new Element(listNodeName);
					e.setAttribute(LINK_ATTR_NAME, describedURL.getLink());
					e.setAttribute(LABEL_ATTR_NAME, describedURL.getLabel());
					urls.addContent(e);
				}
			}
			if (!urls.getChildren().isEmpty())
			{
				parent.addContent(urls);
			}
		}

	}

	private static void addStringList(Element parent, List<String> list, String listeParentNodeName, String listNodeName) 
	{
		if ((list != null) && (!list.isEmpty()))
		{
			Iterator<String> iterator = list.iterator();
			Element strings = new Element(listeParentNodeName);
			parent.addContent(strings);
			while (iterator.hasNext()) 
			{
				String current = (String) iterator.next();
				Element e = new Element(listNodeName);
				e.setText(current);
				strings.addContent(e);
			}
		}

	}

	private static void addNonNullContent(Element parent, String nodeName, String content)
	{
		if (StringUtils.isNotEmpty(content))
		{
			Element e = new Element(nodeName);
			e.setText(content);
			parent.addContent(e);
		}
	}

	private static void addNonNullAttribute(Element parent, String attributeName, String content)
	{
		if (content.length()>0)
		{
			parent.setAttribute(attributeName, content);
		}
	}

}
